﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.ComponentModel;

namespace BizElements.Core
{
    internal static class PkUtil
    {
        #region CreatePkFromFk.

        /// <summary>
        /// Creates an object array which represents PK with the FK values stored in the given child.
        /// </summary>
        /// <returns>PK or <b>null</b> if child's FK is not set.</returns>
        /// <remarks>An exception is generated if a child contains an invalid, i.e. partially set, FK.
        /// Either all, or none of the FK properties must be set.</remarks>
        internal static object[] CreatePkFromFk(IEntity child, DbRelation fk)
        {
            object[] pk;
            if (fk.ChildForeignKey.Length == 1)
                pk = CreateSingleFieldPk(child, fk);
            else
                pk = CreateCompositePk(child, fk);

            return pk;
        }

        private static object[] CreateSingleFieldPk(IEntity child, DbRelation fk)
        {
            object[] pk;
            // Complex algorithm not required if simple FK is used.
            object fkVal = child.GetField(fk.ChildForeignKey[0]);
            pk = (fkVal != null) ? new object[] { fkVal } : null;
            return pk;
        }

        private static object[] CreateCompositePk(IEntity child, DbRelation fk)
        {
            // Child and parent columns in the relation are in the same order.
            // However, that order may not match the order of PK definition in the parent's IDbTable class.
            // Thus, the FK values may have to be reordered to be used as PK.
            IDbColumn[] orderedPkFields = fk.Parent.PrimaryKey.ToArray();
            object[]  pk = new object[orderedPkFields.Length];
            int nullValCount = 0;
            for (int idxOrderedPkPart = 0; idxOrderedPkPart < orderedPkFields.Length; idxOrderedPkPart++)
            {
                IDbColumn currPkField = orderedPkFields[idxOrderedPkPart];
                int positionOfCurrentPkColumnInRelation = IndexOfColumnInArray(fk.ParentPrimaryKey, currPkField.ColumnName);
                IDbColumn currFkField = fk.ChildForeignKey[positionOfCurrentPkColumnInRelation];
                object fkPart = child.GetField(currFkField);
                pk[idxOrderedPkPart] = fkPart;

                if (fkPart == null)
                    nullValCount++;
            }

            bool fkIsSet = (nullValCount == 0);
            if (!fkIsSet)
            {
                bool allFkFieldsAreNull = (nullValCount == orderedPkFields.Length);
                if (allFkFieldsAreNull)
                    pk = null;
                else
                    throw new ArgumentException(Messages.PkUtil_FkMustBeCompleteOrNull, "child");
            }

            return pk;
        }

        private static int IndexOfColumnInArray(IDbColumn[] array, string columnName)
        {
            for (int idx = 0; idx < array.Length; idx++)
            {
                if (array[idx].ColumnName == columnName)
                    return idx;
            }

            return -1;
        }

        #endregion

        #region GetPrimaryKey as dictionary.

        /// <summary>Gets a dictionary that contains primary key field identifiers/names and value.</summary>
        /// <param name="entity">Entity.</param>
        /// <param name="dictionaryKeyProperty">Defines which property will be used as dictionary key.</param>
        /// <returns>Dictionary with primary key values.</returns>
        internal static IDictionary<string, object> GetPrimaryKey(IEntity entity, FieldIdentifierProperty dictionaryKeyProperty)
        {
            Dictionary<string, object> pk = new Dictionary<string, object>();
            switch (dictionaryKeyProperty)
            {
                case FieldIdentifierProperty.PropertyName:
                    foreach (IDbColumn pkPart in entity.Table.Columns)
                        pk[pkPart.PropertyName] = entity.GetField(pkPart);
                    break;

                case FieldIdentifierProperty.ColumnName:
                    foreach (IDbColumn pkPart in entity.Table.Columns)
                        pk[pkPart.ColumnName] = entity.GetField(pkPart);
                    break;

                case FieldIdentifierProperty.Alias:
                    foreach (IDbColumn pkPart in entity.Table.Columns)
                        pk[pkPart.Alias] = entity.GetField(pkPart);
                    break;

                default:
                    throw new ArgumentException(Messages.EntityModelBase_UnsupportedFieldIdentifierOption, "dictionaryKeyProperty");
            }

            return pk;
        }

        #endregion
    }
}